Vue 3 和 React 的虚拟 DOM 在核心概念上类似,但在实现细节、优化策略和使用方式上存在显著差异。以下是两者的关键对比:
一、核心实现差异
1. Diff 算法
Vue 3:
- 使用 预处理 + 最长递增子序列(LIS 算法,时间复杂度为 O(n log n)
- 优先处理相同前置 / 后置元素,快速跳过无需比较的节点
- 通过
ShapeFlag
位运算快速判断节点类型
React:
- 使用双指针遍历 + key 比较,默认时间复杂度为 O(n)
- 依赖
key
属性识别同层节点变化 - 2020 年后引入Fiber 架构,将渲染任务拆分为小单元(可中断)
javascript1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208// Vue 3的Diff算法(简化)
// 1. 预处理相同前置/后置节点
// 2. 处理新增/删除节点
// 3. 使用LIS算法计算最小移动次数
function patchKeyedChildren(c1, c2, container, parentAnchor) {
let i = 0;
const l1 = c1.length;
const l2 = c2.length;
let e1 = l1 - 1; // 旧节点的结束索引
let e2 = l2 - 1; // 新节点的结束索引
// 1. 预处理相同前置节点
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
const n1 = c1[i];
const n2 = c2[i];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container);
} else {
break;
}
i++;
}
// 2. 预处理相同后置节点
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1];
const n2 = c2[e2];
if (isSameVNodeType(n1, n2)) {
patch(n1, n2, container);
} else {
break;
}
e1--;
e2--;
}
// 3. 处理新增节点(旧节点已遍历完,新节点有剩余)
// (a b)
// (a b) c d
// i = 2, e1 = 1, e2 = 3
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1;
const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
while (i <= e2) {
patch(null, c2[i], container, anchor);
i++;
}
}
}
// 4. 处理删除节点(新节点已遍历完,旧节点有剩余)
// (a b) c d
// (a b)
// i = 2, e1 = 3, e2 = 1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i]);
i++;
}
}
// 5. 处理乱序节点(核心Diff)
// a b [c d e] f g
// a b [e d c h] f g
else {
const s1 = i; // 旧节点的开始索引
const s2 = i; // 新节点的开始索引
// 5.1 建立新节点的key到index的映射
const keyToNewIndexMap = new Map();
for (i = s2; i <= e2; i++) {
const nextChild = c2[i];
if (nextChild.key !== null) {
keyToNewIndexMap.set(nextChild.key, i);
}
}
// 5.2 遍历旧节点,寻找匹配的新节点
let j;
let patched = 0;
const toBePatched = e2 - s2 + 1;
let moved = false;
let maxNewIndexSoFar = 0;
const newIndexToOldIndexMap = new Array(toBePatched).fill(0);
for (i = s1; i <= e1; i++) {
const prevChild = c1[i];
if (patched >= toBePatched) {
// 所有新节点都已处理,剩余旧节点全部删除
unmount(prevChild);
continue;
}
let newIndex;
if (prevChild.key !== null) {
// 通过key查找新节点位置
newIndex = keyToNewIndexMap.get(prevChild.key);
} else {
// 没有key,遍历查找
for (j = s2; j <= e2; j++) {
if (
newIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2[j])
) {
newIndex = j;
break;
}
}
}
if (newIndex === undefined) {
// 没有找到匹配的新节点,删除当前旧节点
unmount(prevChild);
} else {
// 保存旧节点索引(+1 是为了避免与默认值0冲突)
newIndexToOldIndexMap[newIndex - s2] = i + 1;
// 判断节点是否需要移动
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex;
} else {
moved = true;
}
// 复用旧节点,更新内容
patch(prevChild, c2[newIndex], container);
patched++;
}
}
// 5.3 使用LIS算法计算最小移动次数
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: [];
j = increasingNewIndexSequence.length - 1;
// 5.4 移动和插入节点
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i;
const nextChild = c2[nextIndex];
const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;
if (newIndexToOldIndexMap[i] === 0) {
// 新节点,需要插入
patch(null, nextChild, container, anchor);
} else if (moved) {
// 需要移动节点
if (j < 0 || i !== increasingNewIndexSequence[j]) {
move(nextChild, container, anchor);
} else {
j--;
}
}
}
}
}
// 判断两个VNode是否可以复用(key和type都相同)
function isSameVNodeType(n1, n2) {
return n1.type === n2.type && n1.key === n2.key;
}
// 最长递增子序列算法(Vue 3源码实现)
function getSequence(arr) {
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
const arrI = arr[i];
if (arrI !== 0) {
j = result[result.length - 1];
if (arr[j] < arrI) {
p[i] = j;
result.push(i);
continue;
}
u = 0;
v = result.length - 1;
while (u < v) {
c = (u + v) >> 1;
if (arr[result[c]] < arrI) {
u = c + 1;
} else {
v = c;
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1];
}
result[u] = i;
}
}
}
u = result.length;
v = result[u - 1];
while (u-- > 0) {
result[u] = v;
v = p[v];
}
return result;
}
1 | // React的Diff算法(简化) |
2. 渲染优化
Vue 3:
- 编译时优化:静态提升(Static Hoisting)、Block Tree
- 自动标记动态节点,减少 Diff 范围
- 事件处理函数缓存(
cacheHandlers
)
React:
- 运行时优化:依赖
React.memo
、useMemo
、useCallback
等手动优化 - 需要开发者主动控制组件更新(如
shouldComponentUpdate
) - 引入Concurrent Mode(实验性)实现优先级渲染
- 运行时优化:依赖
二、实现策略差异
1. 模板 vs JSX
Vue 3:
主要使用模板语法(
.vue
文件)编译时生成优化的渲染函数
示例:
vue
1
2
3<template>
<div>{{ message }}</div>
</template>
React:
主要使用JSX(JavaScript 语法扩展)
运行时编译 JSX 为
React.createElement
调用示例:
jsx
1
2
3function App() {
return <div>{message}</div>
}
2. 响应式系统
Vue 3:
内置Proxy-based 响应式系统
自动追踪依赖,精确触发更新
示例:
javascript
1
2
3import { reactive } from 'vue'
const state = reactive({ count: 0 })
// state.count变化时自动触发更新
React:
使用不可变数据和状态管理库(如 Redux)
通过
setState
或useState
显式触发更新示例:
javascript
1
2const [count, setCount] = useState(0)
// 必须调用setCount才能触发更新
三、性能优化差异
1. 静态内容处理
Vue 3:
编译时识别静态节点并提升(Static Hoisting)
静态节点只创建一次,后续渲染直接复用
示例:
vue
1
2
3
4<div>
<h1>Static Title</h1> <!-- 编译时提升 -->
<p>{{ dynamic }}</p>
</div>
React:
- 没有编译时优化,静态节点每次渲染都会重新创建
- 需手动使用
React.memo
或提取组件避免重复渲染
2. 事件处理优化
Vue 3:
自动缓存事件处理函数(
cacheHandlers
)示例:
vue
1
2<button @click="handleClick">Click</button>
<!-- 编译后自动缓存handleClick -->
React:
需要手动使用
useCallback
缓存回调函数示例:
jsx
1
2
3const handleClick = useCallback(() => {
// 处理逻辑
}, [dependencies])
四、架构设计差异
1. 组件更新粒度
Vue 3:
- 组件级更新:单个组件状态变化只会触发该组件重新渲染
- 基于响应式系统精确追踪依赖
React:
- 函数式组件默认全量重新渲染
- 需要通过
React.memo
、useMemo
等手动控制
2. 异步渲染
Vue 3:
- 渲染过程是同步的,但更新是异步批量的
- 通过
nextTick
访问更新后的 DOM
React:
- Concurrent Mode(实验性)支持异步渲染
- 可中断渲染过程,优先处理高优先级任务
五、总结对比表
特性 | Vue 3 | React |
---|---|---|
Diff 算法 | 预处理 + LIS(O (n log n)) | 双指针 + key(O (n)) |
优化方式 | 编译时自动优化(静态提升、Block) | 运行时手动优化(React.memo) |
响应式系统 | 内置 Proxy-based 响应式 | 不可变数据 + 显式 setState |
模板语法 | 声明式模板 | JSX |
更新粒度 | 组件级 | 函数式组件默认全量更新 |
异步渲染 | 异步批量更新 | Concurrent Mode(实验性) |
六、适用场景
Vue 3:
- 适合需要高性能渲染的大型应用
- 对开发效率有较高要求(编译时优化减少手动工作)
- 偏好声明式模板语法的开发者
React:
- 适合需要灵活控制渲染过程的应用
- 团队熟悉 JavaScript / 函数式编程
- 需要与复杂状态管理库集成的场景